use cgmath::Vector2;
use minifb::{Key, Window};

use crate::camera::*;

/// # Warning!!!
/// # Do NOT modify manually through the pointer!
/// # Use the query() function
#[repr(C)]
pub struct KeyboardState {
    pub keyboard_state: *mut u8,
    pub external: bool,
}

impl KeyboardState {
    pub fn new() -> Self {
        use std::alloc::{alloc_zeroed, Layout};
        Self {
            keyboard_state: unsafe {
                alloc_zeroed(Layout::array::<u8>(108).unwrap())
            },
            external: false,
        }
    }

    pub fn query(
        &mut self,
        window: &Window
    ) {
        let pressed = window.get_keys();
        for key in 0..108 {
            unsafe {
                (*self.keyboard_state.add(key)) = 0;
            }
        }
        for &key in pressed.iter() {
            unsafe {
                // println!("{} is pressed", key as i32);
                (*self.keyboard_state.add(key as usize)) = 1;
            }
        }
    }

    pub fn is_key_pressed(&self, key: Key) -> bool {
        unsafe {
            // println!("{}", self.keyboard_state as i128);
            *self.keyboard_state.add(key as i32 as usize) != 0
        }
    }

    /// # Safety
    /// Make sure that the size is 108, for that is the total count of keys
    pub unsafe fn from_raw_ptr(ptr: *mut u8) -> Self {
        Self {
            keyboard_state: ptr,
            external: true,
        }
    }

    pub fn as_raw_ptr(
        &self,
    ) -> *mut u8 {
        self.keyboard_state
    }
}

impl Drop for KeyboardState {
    fn drop(&mut self) {
        if self.external {
            return;
        }
        use std::alloc::{dealloc, Layout};
        unsafe {
            dealloc(
                self.keyboard_state,
                Layout::array::<u8>(108).unwrap()
            );
        }
    }
}

pub(crate) fn is_point_in_rectangle(
    point: &Vector2<f64>,
    leftup: &Vector2<f64>,
    rightdown: &Vector2<f64>,
) -> bool {
    if leftup.x <= point.x && point.x <= rightdown.x &&
    rightdown.y <= point.y && point.y <= leftup.y {
        true
    } else {
        false
    }
}

pub(crate) fn are_2rectangles_intersected(
    leftup_1: &Vector2<f64>,
    rightdown_1: &Vector2<f64>,
    leftup_2: &Vector2<f64>,
    rightdown_2: &Vector2<f64>,
) -> bool {
    let points_1 = vec![
        *leftup_1,
        Vector2::new(leftup_1.x, rightdown_1.y),
        *rightdown_1,
        Vector2::new(rightdown_1.x, leftup_1.y),
    ];
    let points_2 = vec![
        *leftup_2,
        Vector2::new(leftup_2.x, rightdown_2.y),
        *rightdown_2,
        Vector2::new(rightdown_2.x, leftup_2.y),
    ];

    for p1 in points_1.iter() {
        if is_point_in_rectangle(p1, leftup_2, rightdown_2) {
            return true;
        }
    }
    for p2 in points_2.iter() {
        if is_point_in_rectangle(p2, leftup_1, rightdown_1) {
            return true;
        }
    }

    false
}

#[allow(unused)]
pub(crate) fn is_visible(
    center_point: &Vector2<f64>,
    camera: &Camera,
) -> bool {
    let camera_size = &camera.size;
    let camera_center = &camera.center;
    let camera_leftup = camera_center - 0.5 * camera_size;
    let camera_rightdown = camera_center + 0.5 * camera_size;

    if is_point_in_rectangle(center_point, &camera_leftup, &camera_rightdown) {
        true
    } else {
        false
    }
}

pub(crate) fn keep_obstruction(
    center_point: &Vector2<f64>,
) -> bool {
    if -600.0 <= center_point.x && center_point.x <= 600.0
    && -400.0 <= center_point.y && center_point.y <= 400.0 {
        true
    } else {
        false
    }
}

#[allow(unused)]
pub(crate) fn is_in_boundary(
    center_point: &Vector2<f64>,
) -> bool {
    if -500.0 <= center_point.x && center_point.x <= 500.0
    && -300.0 <= center_point.y && center_point.y <= 300.0 {
        true
    } else {
        false
    }
}

pub(crate) fn world_to_screen(
    point_world: &Vector2<f64>,
    camera: &Camera,
    screen_size: &Vector2<f64>
) -> Vector2<f64> {
    let camera_size = camera.size;
    let camera_center = camera.center;
    // let camera_leftup = camera_center - 0.5 * camera_size;
    // let camera_rightdown = camera_center + 0.5 * camera_size;

    let point_camera = point_world - camera_center;

    let point_clip = Vector2::new(
        2.0 * point_camera.x / camera_size.x,
        2.0 * point_camera.y / camera_size.y,
    );

    let point_screen = Vector2::new(
        0.5 * (point_clip.x + 1.0) * screen_size.x,
        screen_size.y - 0.5 * (point_clip.y + 1.0) * screen_size.y,
    );
    
    point_screen
}

pub fn get_sign(x: f64) -> f64 {
    if x == 0.0 { 0.0 }
    else if x < 0.0 { -1.0 }
    else { 1.0 }
}

pub fn get_digits(mut x: u64) -> u64 {
    if x == 0 {
        return 1;
    }
    let mut result = 0;
    loop {
        x /= 10;
        result += 1;

        if x == 0 {
            break;
        }
    }

    result
}

#[allow(dead_code)]
pub fn gaussian(x: f64, variance: f64, mean: f64) -> f64 {
    use std::f64::consts::PI;
    (1.0 / f64::sqrt(2.0 * PI * variance)) * f64::exp(- (x - mean).powi(2) / 2.0 * variance)
}
